package org.sunyaxing.gitlab.gitlabaireview;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HtmlUtil;
import com.alibaba.fastjson2.JSONObject;
import com.openai.client.OpenAIClient;
import com.openai.models.chat.completions.ChatCompletion;
import com.openai.models.chat.completions.ChatCompletionCreateParams;
import com.openai.models.chat.completions.ChatCompletionStreamOptions;
import org.gitlab4j.api.GitLabApi;
import org.gitlab4j.api.GitLabApiException;
import org.gitlab4j.api.models.Diff;
import org.gitlab4j.api.models.MergeRequest;
import org.gitlab4j.api.models.Note;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@RestController
@RequestMapping("/api")
public class GitlabHookController implements ApplicationRunner {
    private static final Logger log = LoggerFactory.getLogger(GitlabHookController.class);

    @Autowired
    private GitLabApi gitLabApi;
    @Autowired
    private OpenAIClient openAIClient;
    @Value("${mr.model:qwq-32b}")
    private String model;
    @Value("${mr.temperature:0.1}")
    private Double temperature;
    @Value("${mr.maxTokens:8196}")
    private Long maxTokens;
    @Value("${mr.states:preparing}")
    private List<String> states;
    private static final String prompt;

    public static final LinkedBlockingDeque<ReviewMsg> MSG_QUEUE = new LinkedBlockingDeque<>(1000);

    static {
        prompt = FileUtil.readString("/opt/transflow/ai/prompt.txt", "UTF-8");
    }

    @GetMapping("/projects")
    public String projects() {
        try {
            return this.gitLabApi.getProjectApi().getProjects().toString();
        } catch (Exception e) {
            log.error("GitlabAiReviewCodeOutputExt", e);
        }
        return "f";
    }

    @GetMapping("/diffs")
    public String diffs(@RequestParam("pid") Long projectId, @RequestParam("iid") Long mergeRequestIid) {
        log.info("GitlabAiReviewCodeOutputExt: {}+>{}", projectId, mergeRequestIid);
        try {
            MergeRequest mergeRequestDiffs = this.gitLabApi.getMergeRequestApi().getMergeRequestChanges(projectId, mergeRequestIid);
            return mergeRequestDiffs.toString();
        } catch (Exception e1) {
            log.error("GitlabAiReviewCodeOutputExt1", e1);
        }
        return "f";
    }

    @GetMapping("/aireview")
    public String aireview(@RequestParam("pid") Long projectId, @RequestParam("iid") Long mergeRequestIid) {
        try {
            review(projectId, mergeRequestIid, new AtomicInteger(0));
        } catch (Exception e) {
            log.error("GitlabAiReviewCodeOutputExt", e);
        }
        return "f";
    }


    @PostMapping("/review")
    public ResponseEntity<String> review(@RequestBody JSONObject mergeRequestEvent) {
        JSONObject project = mergeRequestEvent.getJSONObject("project");
        JSONObject objectAttr = mergeRequestEvent.getJSONObject("object_attributes");
        String state = objectAttr.getString("merge_status");
        // 获取 projectId 和 mergeRequestIid
        Long projectId = project.getLong("id");
        Long mergeRequestIid = objectAttr.getLong("iid");
        if (states.contains(state)) {
            ReviewMsg reviewMsg = new ReviewMsg(projectId, mergeRequestIid);
            MSG_QUEUE.add(reviewMsg);
            log.info("【P[{}] R[{}]】 已加入待 review 队列 , 剩余 {}", projectId, mergeRequestIid, MSG_QUEUE.size());
            return ResponseEntity.ok("ok");
        } else {
            log.info("【P[{}] R[{}]】 MERGE 状态{}不在控制范围{}内 ...", projectId, mergeRequestIid, state, states);
            return ResponseEntity.badRequest().body(state + "is not in " + states);
        }
    }

    public boolean review(Long projectId, Long mergeRequestIid, AtomicInteger retry) throws GitLabApiException {
        int sleepTime = retry.get() * 2;
        log.info("【P[{}] R[{}]】 准备{}s后检查", projectId, mergeRequestIid, sleepTime);
        ThreadUtil.sleep(sleepTime, TimeUnit.SECONDS);
        log.info("【P[{}] R[{}]】 获取差异中...", projectId, mergeRequestIid);
        MergeRequest mergeRequestDiffs = this.gitLabApi.getMergeRequestApi().getMergeRequestChanges(projectId, mergeRequestIid);
        if (CollectionUtil.isEmpty(mergeRequestDiffs.getChanges())) {
            log.info("【P[{}] R[{}]】 未获取到差异部分，准备重试第 {} 次", projectId, mergeRequestIid, retry.incrementAndGet());
            if (retry.get() > 3) {
                log.info("【P[{}] R[{}]】 尝试3次失败，跳过", projectId, mergeRequestIid);
                return false;
            } else {
                return review(projectId, mergeRequestIid, retry);
            }
        } else {
            log.info("【P[{}] R[{}]】 成功获取差异", projectId, mergeRequestIid);
            for (Diff diff : mergeRequestDiffs.getChanges()) {
                String diffStr = diff.getDiff();
                // 差异部分代码让ai出意见
                ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
                        .addSystemMessage(prompt)
                        .streamOptions(ChatCompletionStreamOptions.builder().build())
                        .addUserMessage(diffStr)
                        .model(model)
                        .temperature(temperature)
                        .maxCompletionTokens(maxTokens)
                        .build();
                log.info("【P[{}] R[{}]】 AI 处理中", projectId, mergeRequestIid);
                ChatCompletion chatCompletion = openAIClient.chat().completions().create(params);
                chatCompletion.choices().forEach(choice -> {
                    Optional<String> content = choice.message().content();
                    log.info("【P[{}] R[{}]】 AI 处理结果 {}", projectId, mergeRequestIid, content.get());
                    if (content.get().contains("SKIP_CODE")) {
                        log.info("【P[{}] R[{}]】 代码无异常跳过评论 ", projectId, mergeRequestIid);
                    } else {
                        String contentStr = HtmlUtil.removeHtmlTag(content.get(), "think");
                        if (contentStr.contains("</think>")) {
                            contentStr = CollectionUtil.getLast(StrUtil.split(contentStr, "</think>"));
                        }
                        try {
                            log.info("【P[{}] R[{}]】 添加代码评论", projectId, mergeRequestIid);
                            Note comment = gitLabApi.getNotesApi().createMergeRequestNote(
                                    projectId,
                                    mergeRequestIid,
                                    contentStr
                            );
                        } catch (Exception e) {
                            log.info("【P[{}] R[{}]】 评论失败 ...", projectId, mergeRequestIid, e);
                        }
                    }
                });
            }
            return true;
        }
    }

    // 创建拒绝线程池，队列满时，会在当前线程执行
    private static ThreadPoolExecutor executor = new ThreadPoolExecutor(
            5, 5, 0L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100),
            new ThreadPoolExecutor.CallerRunsPolicy()
    );

    @Override
    public void run(ApplicationArguments args) throws Exception {
        ThreadUtil.execute(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    ReviewMsg reviewMsg = MSG_QUEUE.poll(2, TimeUnit.SECONDS);
                    if (Objects.nonNull(reviewMsg)) {
                        executor.execute(() -> {
                            Long projectId = reviewMsg.getProjectId();
                            Long mergeRequestIid = reviewMsg.getMergeRequestIid();
                            try {
                                review(projectId, mergeRequestIid, new AtomicInteger(0));
                            } catch (Exception e) {
                                log.info("【P[{}] R[{}]】 检查失败 ...", projectId, mergeRequestIid, e);
                            }
                        });
                    }
                } catch (InterruptedException e) {
                    log.error("线程已被销毁", e);
                }
            }
        });
    }
}
